# THIS IS A SOURCE CODE FILE FROM I'M NOT EVEN HUMAN THE GAME.
# IT COULD BE USED IN A DIFFERENT PIECE OF SOFTWARE ( LIKE A
# DIFFERENT GAME ), BUT IT WAS ORIGINALLY WRITTEN FOR I'M NOT
# EVEN HUMAN THE GAME.

# THE DEVELOPERS OF THE GAME ARE : (C) J.Y.AMIHUD, AYYZEE AND 
# OTHER CONTRIBUTORS. THIS AND OTHER FILES IN THIS GAME,
# UNLESS SPECIFICALLY NOTED, COULD BE USED UNDER THE TERMS OF
# GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER VERSION.

import os
import json
import cairo


def load_palete(game):

    """Loads colors from assets/palete.json"""

    with open("assets/palete.json") as f:
        game.palete = json.load(f)

def distance(list1, list2):

    """Calculates distnaces between two vectors"""
    d = []
    for n, i in enumerate(list1):
        try:
            d.append(max((i-list2[n]),(list2[n]-i)))
        except:
            d.append(0)
    return sum(d) / len(d)

def color(game, layer, code, alpha=1):

    """Sets a cairo brush color from a palete by looking for the
       closest match."""

    # If it's a code in hex like #FF0000 so we need to convert it
    # to numbers first

    if type(code) == str and code.startswith("#") and (len(code) -1) %3 == 0:
        new_c = []
        for c in range(3):
            ind = int((len(code)-1)/3)
            new_c.append( int(code[ind*c+1:ind*c+ind+1], 16)/255 )
        code = new_c
    # If it's a code in RGB, we look for the nearest
    # color on the palete.

    
    if type(code) == list or type(code) == tuple:

        name = "black"     # Place holder for a palete entry
        dist = 10000000000 # Imaginary large number

        # Chekcing for the lowest possible deviation of color
        for color in game.palete:
            new_dist = distance(game.palete[color], code)
            if dist > new_dist:
                name = color
                dist = new_dist
        r, g, b = game.palete[name] 

    elif type(code) == str:
        try:
            r, g, b = game.palete[code] 
        except:
            r, g, b = 0, 0, 0

    else:
        r, g, b = 0, 0, 0

    layer.set_source_rgba(r,g,b,alpha)

def cache_sprite_sheet(game, sheet, asset=False):

    """This function will cache sprite sheets into game.images"""

    if sheet not in game.images:

        # Loading the sheet image
        grid = cairo.ImageSurface.create_from_png(sheet)

        # Loading metadata
        try:
            with open(sheet.replace(".png", ".json")) as f:
                metadata = json.load(f)
        except:
            metadata = {}
            
        width = 1
        height = 1
        try:
            width = metadata["resolution"][0]
            height = metadata["resolution"][1]
        except:
            pass
            
            
        # Getting the resolution of the cell
        cellx = int(grid.get_width()/width) 
        celly = int(grid.get_height()/height)

        data = {}

        if asset:
          
            game.elements[asset[0]][asset[1]] = []
        
        for x in range(width):
            for y in range(height):

                # Creating individual cells
                cell = cairo.ImageSurface(cairo.FORMAT_ARGB32,
                                          cellx,
                                          celly)
                drawcell = cairo.Context(cell)
                drawcell.set_antialias(cairo.ANTIALIAS_NONE)
                drawcell.set_source_surface(grid,
                                            1-(cellx*x+1),
                                            1-(celly*y+1))
                drawcell.paint()

                # Putting the cell into the data as in "0:0" or "43:69".
                cellname = str(x)+":"+str(y)
                cellnameindata = cellname
                celldata = {}
                try:

                    if asset:
                        print(asset)
                        game.elements[asset[0]][asset[1]].append(metadata[cellname])

                    cellnameindata = metadata[cellname]["title"]
                    celldata = metadata[cellname]
                except:
                    pass
                
                data[cellnameindata] = [cell, celldata]
        try:
            data["title"] = metadata["title"]
        except:
            data["title"] = sheet
        game.images[sheet] = data
    
def image(game, layer, x, y, name, code="0:0", color=False, offset=False, alpha=1):

    x = int(x)
    y = int(y)
    
    """This function draws an image into a canvas. And load it if
       it's not loaded"""

    # Load the whole image as a sprite ( if it's not loaded yet )
    cache_sprite_sheet(game, name)

    # Drawing the image

    source = game.images[name].get(code,
                                   list(game.images[name].values())[-2])

    if offset:
        off = source[1].get("offset", [0,0])
        x += off[0]
        y += off[1]
   
    
    if color: #If we forcing color
        layer.set_antialias(cairo.ANTIALIAS_NONE)
        layer.mask_surface(source[0],x, y)
        layer.fill()
    else:     #If we just using the image
        layer.set_source_surface(source[0], x, y)
        if alpha == 1:
            layer.paint()
        else:
            layer.paint_with_alpha(alpha)
        
def text(game, layer, string, x, y, color=True, align="left"):

    """This function will draw text using an assets/font.png file."""

    for n, l in enumerate(string):
        ax =  x+(8*n)
        if align == "center":
            ax = ax - int(len(string)*4)
        elif align == "right":
            ax = ax - int(len(string)*8)
        image(game, layer, ax, y, "assets/font.png", l, color=color)
                
def button(game, layer, x, y, w, h,
           menu="", icon="", string="", func=False,
           scroll="", borders=True):

    """Draws a button"""

    

    # Let's add this button into a menu.

    menu_selected = False
    select_number = 0
    
    if menu:
        if menu not in game.menus :
            game.menus[menu] = {"selected":0,
                                "buttons":[]}


        bmenudata = [x,y]

        if bmenudata not in game.menus[menu]["buttons"]:
            game.menus[menu]["buttons"].append(bmenudata)
        
        for n, i in enumerate(game.menus[menu]["buttons"]):
            
            if i == bmenudata:
                
                select_number = n
                if n == game.menus[menu]["selected"]:
                    menu_selected = True

    if scroll and scroll in game.scroll:
       

        # Making sure that while I press either UP or DOWN it will
        # scroll to the correct spot.

        if (65362 in game.current["keys"]\
        or 65364 in game.current["keys"])\
        and menu_selected:
            game.scroll[scroll] = 0 - y + int(game.current["h"] / 2)

        y = y + int(game.scroll[scroll])
    
        
    x = int(round(x))
    y = int(round(y))
    w = int(round(w))
    h = int(round(h))

    
        
    color(game, layer, "#00FF00")
    layer.set_line_width(1)
    if borders:
        layer.rectangle(x,y,w,h)
        layer.stroke()

    mo = False
    if int(game.current["mx"]) in range(x, x+w) \
    and int(game.current["my"]) in range(y, y+h):
        mo = True
        
    if mo or menu_selected:
        if menu:
            game.menus[menu]["selected"] = select_number
        
        if mo:
            layer.rectangle( x+1,y+1, w-3, h-3 )
            layer.fill()
        else:
            layer.rectangle( x+1,y+1, w-2, h-2 )
            layer.stroke()

    # If you clicked the button
    if ( game.previous["LMB"] \
         and int(game.previous["LMB"][0]) in range(x, x+w) \
         and int(game.previous["LMB"][1]) in range(y, y+h) ) \
         or ( menu_selected and 65293 in game.previous["keys"]):

        color(game, layer, "yellow")
        layer.rectangle( x+1,y+1, w-3, h-3 )
        layer.fill()

        # If the is doing something
        if (( not game.current["LMB"] and game.previous["LMB"] and mo) \
             or (65293 not in game.current["keys"] \
                 and 65293 in game.previous["keys"])) \
                 and func:
            func()
            game.previous["LMB"] = False
            game.current["LMB"]  = False
            game.current["keys"] = []
            game.previous["keys"] = []

    if icon:
        if mo:
            color(game, layer, "black")
        image(game, layer, x+2,y+1,"assets/menu_icons.png",
              icon, True)
        
    if string:
        if mo:
            color(game, layer, "black")
            
        widthleft = w - 4
        xd = x
        if icon:
            widthleft -= 12
            xd += 12
        sl = int(widthleft / 8)
        if len(string)*8 > widthleft:
            
            fp = int(game.current["frame"]/10) % (len(string)+7)
            text(game, layer, (string+"       "+string)[fp:fp+sl], xd+2, y-int(h/5)+3 )
        #elif len(string)*8 > widthleft:
        #    text(game, layer, string[:sl-3]+"...", xd+2, y-int(h/5)+3 )
            
        else:
            text(game, layer, string, x+int(w/2), y-int(h/5)+3, align="center")
        
def button_navigate(game, menu):

    """This function will run in each layer to provide keyboard navigation."""

    # We don't want to run it if non of the arrow keys are pressed.
    # Thus we check for either of the arrow keys is pressed.
    if 65361 in game.current["keys"]\
    or 65362 in game.current["keys"]\
    or 65363 in game.current["keys"]\
    or 65364 in game.current["keys"]:

        # We want to know things about the currently selected button
        select = game.menus[menu]["selected"] # It's number in the list
        cur    = game.menus[menu]["buttons"][select] # It's coordinates x, y
        prevdistance = 100000000000 # Arbitrary huge number

        # For all buttons in the menu list, we are going to see if it's
        # in a correct direction from the currently selected button, based on
        # the arrow key:
        #      65361 - RIGHT
        #      65362 - UP
        #      65363 - LEFT
        #      65364 - DOWN
        # But since there could be more then one button towards the correct
        # side, we are also checking for the closest one ( by comparing the
        # lowest possible distance to the currently selected button ).
        
        for n, i in enumerate( game.menus[menu]["buttons"] ):
            curdistance = distance(i, cur)
            if ((65361 in game.current["keys"] and i[0] < cur[0] )\
            or  (65362 in game.current["keys"] and i[1] < cur[1] )\
            or  (65363 in game.current["keys"] and i[0] > cur[0] )\
            or  (65364 in game.current["keys"] and i[1] > cur[1] ))\
            and (curdistance < prevdistance)\
            and i != cur:
                select = n
                prevdistance = curdistance

        # And we restart keys, so it will not do this on each frame
        game.current["keys"] = []
        # And we write the number of the selected key
        game.menus[menu]["selected"] = select
            
            
def scroll_area(game, layer, menu, x, y, width, height, max_value,
                strenght=6):

    """This function makes an invisible area in the UI, where there
    could be scroolling."""

    
    if max_value == 0:
        max_value = 1
    
    x = int(x)
    y = int(y)
    width = int(width)
    height = int(height)
    max_value += 5

    amount = 0.0
    if  int(game.current['mx']) in range(x, x+width) \
    and int(game.current['my']) in range(y, y+height):
    
        amount = game.current["scroll"][1] * strenght
        game.current["scroll"] = [0, 0]

        # Middle mouse drag ( for those who have no working wheel
        # like myself, lol )
        
        if game.current["MMB"]\
        and int(game.current["mx"]) in range(x, x+width)\
        and int(game.current["my"]) in range(y, y+height):
            amount = 0- ( game.current["my"] - game.previous["my"] )

    def logic():
        
        # Scroll logic
        game.scroll[menu] -= amount
        
        # If too low  
        if game.scroll[menu] < (1-max_value+height):
            game.scroll[menu] = (1-max_value+height)
        # If too high
        if game.scroll[menu] > 0:
            game.scroll[menu] = 0    

        
            
    logic() 

    if game.current["testing"]:
        color(game, layer, "red")
        layer.rectangle(x,y+1,width-1,height-1)
        layer.stroke()
